﻿function GameManager(size, inputManager, actuator, storageManager) {
    this.size = size;
    this.inputManager = new inputManager;
    this.storageManager = new storageManager;
    this.actuator = new actuator;

    this.serverManager = new ServerManager();

    this.startTiles = 2;
    
    this.inputManager.on("move", this.move.bind(this));
    this.inputManager.on("restart", this.restart.bind(this));
    this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));
    this.inputManager.on("saveName", this.saveName.bind(this));

    this.setup();
}

// 게임 재시작
GameManager.prototype.restart = function() {
    this.storageManager.clearGameState();
    this.actuator.continueGame(); // Clear the game won/lost message
    this.setup();
};

// 2048 점이 넘은 이후 게임을 유지한다
GameManager.prototype.keepPlaying = function() {
    this.keepPlaying = true;
    this.actuator.continueGame(); // Clear the game won/lost message
};

GameManager.prototype.saveName = function () {
    var self = this;
    this.serverManager.saveInfo(this.score, function() {
        self.storageManager.clearGameState();
        self.actuator.continueGame(); // Clear the game won/lost message
        self.setup();
    });
};

// Return true if the game is lost, or has won and the user hasn't kept playing
GameManager.prototype.isGameTerminated = function () {
    return this.over || (this.won && !this.keepPlaying);
};

// 게임이 시작할때 환경을 세팅한다.
GameManager.prototype.setup = function () {
    var previousState = this.storageManager.getGameState();

    if (previousState) {
        this.grid = new Grid(previousState.grid.size,
                                    previousState.grid.cells); // Reload grid
        this.score = previousState.score;
        this.over = previousState.over;
        this.won = previousState.won;
        this.keepPlaying = previousState.keepPlaying;
    } else {
        this.grid = new Grid(this.size);
        this.score = 0;
        this.over = false;
        this.won = false;
        this.keepPlaying = false;

        // Add the initial tiles
        this.addStartTiles();
    }

    this.actuate();

    this.serverManager.getTotalScore();
};

// 게임이 시작할 때 시작타일을 배치한다.
GameManager.prototype.addStartTiles = function() {
    for (var i = 0; i < this.startTiles; i++) {
        this.addRandomTile();
    }
};

// 매 Move 이벤트 후 랜덤한 위치에 Tile을 배치한다.
GameManager.prototype.addRandomTile = function() {
    if (this.grid.cellsAvailable()) {
        var value = Math.random() < 0.9 ? 2 : 4;
        var tile = new Tile(this.grid.randomAvailableCell(), value);

        this.grid.insertTile(tile); 
    }
};

// actuator에게 변경된 그리드 정보를 전달한다.
GameManager.prototype.actuate = function() {
    if (this.storageManager.getBestScore() < this.score) {
        this.storageManager.setBestScore(this.score);
    }

    // Clear the state when the game is over (game over only, not win)
    if (this.over) {
        this.storageManager.clearGameState();
    } else {
        this.storageManager.setGameState(this.serialize());
    }
    
    this.actuator.actuate(this.grid, {
        score: this.score,
        over: this.over,
        won: this.won,
        bestScore: this.storageManager.getBestScore(),
        terminated: this.isGameTerminated()
    });
};

// 현재 게임을 Json Type의 Object로 변환한다.
GameManager.prototype.serialize = function() {
    return {
        grid: this.grid.serialize(),
        score: this.score,
        over: this.over,
        won: this.won,
        keepPlaying: this.keepPlaying
    };
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function () {
    this.grid.eachCell(function(x, y, tile) {
        if (tile) {
            tile.mergedFrom = null;
            tile.savePosition();
        }
    });
};

// 명령대로 타일을 움직인다.
GameManager.prototype.moveTile = function(tile, cell) {
    this.grid.cells[tile.x][tile.y] = null;
    this.grid.cells[cell.x][cell.y] = tile;

    tile.updatePosition(cell);
}

GameManager.prototype.move = function(direction) {
    var self = this;
    
    if (self.isGameTerminated()) return;

    var cell, tile;

    var vector = this.getVector(direction);
    var traversals = this.buildTraversals(vector);
    var moved = false;

    // Save the current tile positions and remove merger information
    this.prepareTiles();
    
    // Traverse the grid in the right direction and move tiles
    traversals.x.forEach(function(x) {
        traversals.y.forEach(function (y) {
            
            cell = { x: x, y: y };
            tile = self.grid.cellContent(cell);

            if (tile) {
                
                var positions = self.findFarthestPosition(cell, vector);
                var next = self.grid.cellContent(positions.next);
                
                // Only one merger per row traversal?
                if (next && next.value === tile.value && !next.mergedFrom) {
                    
                    var merged = new Tile(positions.next, tile.value * 2);
                    merged.mergedFrom = [tile, next];

                    self.grid.insertTile(merged);
                    self.grid.removeTile(tile);

                    // Converge the two tiles' positions
                    tile.updatePosition(positions.next);

                    // Update the score
                    self.score += merged.value;

                    // The mighty 2048 tile
                    if (merged.value === 2048) self.won = true;
                } else {
                    //if (positions.farthest.x == 3 && positions.farthest.y == 2)

                    self.moveTile(tile, positions.farthest);
                }
                
                if (!self.positionsEqual(cell, tile)) {
                    moved = true;
                }
            }
        });
    });


    if (moved) {
        
        this.addRandomTile();
        
        if (!this.movesAvailable()) {
            this.over = true;
        }

        this.actuate();
    }
};

// Get the vector representing the chosen direction
GameManager.prototype.getVector = function (direction) {
    // Vectors representing tile movement
    var map = {
        0: { x: 0, y: -1 }, //up
        1: { x: 1, y: 0 }, //right
        2: { x: 0, y: 1 }, //down
        3: { x: -1, y: 0 }  // left
    }

    return map[direction];
};

// Build a list of positions to traverse in the right order ... 이 기능은 사실 아직 잘 모르겠씀
GameManager.prototype.buildTraversals = function(vector) {
    var traversals = { x: [], y: [] };

    for (var pos = 0; pos < this.size; pos++) {
        traversals.x.push(pos);
        traversals.y.push(pos);
    }

    // Always traverse from the farthest cell in the chosen direction
    if (vector.x === 1) traversals.x = traversals.x.reverse();
    if (vector.y === 1) traversals.y = traversals.y.reverse();

    return traversals;
};

// 
GameManager.prototype.findFarthestPosition = function(cell, vector) {
    var previous;

    do {
        previous = cell;

        cell = {
            x: previous.x + vector.x,
            y: previous.y + vector.y
        };
    } while (this.grid.withinBounds(cell) && this.grid.cellAvailable(cell));

    return {
        farthest: previous,
        next: cell
    };
};

// Check for available matches between tiles (more expensive check)
GameManager.prototype.movesAvailable = function () {
    return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};

// 배치된 위치가 지정된 것과 같으면 True
GameManager.prototype.positionsEqual = function (first, second) {
    return first.x === second.x && first.y === second.y;
};


// Check for available matches between tiles (more expensive check)
GameManager.prototype.tileMatchesAvailable = function () {
    var self = this;

    var tile;

    for (var x = 0; x < this.size; x++) {
        for (var y = 0; y < this.size; y++) {
            tile = this.grid.cellContent({ x: x, y: y });

            if (tile) {
                for (var direction = 0; direction < 4; direction++) {
                    var vector = self.getVector(direction);
                    var cell = { x: x + vector.x, y: y + vector.y };

                    var other = self.grid.cellContent(cell);

                    if (other && other.value === tile.value) {
                        return true; // These two tiles can be merged
                    }
                }
            }
        }
    }

    return false;
};